//
// Unihan Input Method
//
// Copyright © 2002-2007 Sun Wah Linux Ltd.
//
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
// USA
//
// $Id: table.c 816 2007-08-24 16:29:53Z roger $
//

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <unihan_im.h>
#include <unihan_im_keycodes.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <sys/mman.h>
#include <fcntl.h>

#include "table.h"
#include "const.h"
#include "ci.h"
#include "utils.h"
#include "illegibility.h"

static IMInfo *table_get_im_list(void);
static void table_get_im_list_for_path(IMInfo **im_list, char *path);
static int table_read_tabfile_header(IMInfo *im, char *fullpath);
static int table_init(UnihanIM *uim);
static int table_done(UnihanIM *uim);
static int table_create_session(UnihanIM *uim);
static int table_destroy_session(UnihanIM *uim);
static int table_handle_event(UnihanIM *uim, IMEvent *ev);
static int handle_focus_event(UnihanIM *uim, IMFocusEvent *fev);
static int handle_config_change_event(UnihanIM *uim, IMConfigChangeEvent *ev);
#if NEW_PROPERTY
static int handle_property_toggled_event(UnihanIM * uim, IMPropertyToggledEvent * ev);
static int handle_property_register_event(UnihanIM * uim, IMPropertyRegisterEvent * ev);
#endif


static int handle_key_event_idle(UnihanIM *uim, IMKeyEvent *ev);
static int handle_key_event_idle_punctuation(UnihanIM *uim, IMKeyEvent *ev);
static int handle_key_event_preed_sel(UnihanIM *uim, IMKeyEvent *ev);
static int handle_key_event_preediting(UnihanIM *uim, IMKeyEvent *ev);
static int handle_key_event_commiting(UnihanIM *uim, IMKeyEvent *ev);
static int handle_key_event_selecting(UnihanIM *uim, IMKeyEvent *ev);
static void lookup_draw(UnihanIM *uim);
static void lookup_done(UnihanIM *uim);
static void preedit_draw(UnihanIM *uim);
static void preedit_erase(UnihanIM *uim);
static void commit_string(UnihanIM *uim, char *string);
static const unsigned char *utf8_next_char(const unsigned char *in);
static int utf8_charlen(const unsigned char *in);

static int quotation_marks = 0;
static int quotes = 0;


int
unihan_im_module_init(void)
{
	return TRUE;
}


void
unihan_im_module_done(void)
{
	return;
}


int
unihan_im_module_get_info(UnihanIMModuleArgList *args)
{
	if (!args) return FALSE;
	while (args->id) {
		switch (args->id) {
    case UNIHAN_IM_MODULE_ID:
				args->value = strdup("table");
				break;
			case UNIHAN_IM_MODULE_NAME:
				args->value = strdup("Generic Table");
				break;
			case UNIHAN_IM_MODULE_VERSION:
				args->value = strdup("1.0");
				break;
			case UNIHAN_IM_MODULE_IM_LIST:
				args->value = table_get_im_list();
				break;
                        case UNIHAN_IM_API_VERSION:
                                args->value = strdup("2.0");
                                break;
		}
		++args;
	}

	return TRUE;
}


static IMInfo *
table_get_im_list(void)
{
	IMInfo *im = NULL;

	table_get_im_list_for_path(&im, "/usr/lib/unihan2/im_modules/table");
	return im;
}


static void
table_get_im_list_for_path(IMInfo **im_list, char *path)
{
	DIR *dir;
#if 0
	UnihanPropertyArgList new_property;
#endif

	union {
		struct dirent d;
		char b[sizeof(struct dirent) + NAME_MAX + 1];
	} u;
	struct dirent *res = NULL;
	IMMethods *imm = NULL;
	IMInfo *im = NULL;

	dir = opendir(path);
	if (!dir) {
		// FIXME: error handling
		return;
	}

	while (readdir_r(dir, &u.d, &res) == 0 && res != NULL) {
		char *ext = strrchr(res->d_name, (int)'.');
		if (ext && !strcmp(".tab", ext)) {
			//char *tmp;
			char *fullpath;
			fullpath = (char*)malloc(strlen(path)+strlen(res->d_name)+2);
			sprintf(fullpath, "%s/%s", path, res->d_name);

			im = (IMInfo *)malloc(sizeof(IMInfo));
			memset(im, 0, sizeof(IMInfo));
			if (!table_read_tabfile_header(im, fullpath)) {
				free(im);
				free(fullpath);
				continue;
			}

			imm = (IMMethods *)malloc(sizeof(IMMethods));
			memset(imm, 0, sizeof(IMMethods));
			imm->init = table_init;
			imm->done = table_done;
			imm->create_session = table_create_session;
			imm->destroy_session = table_destroy_session;
			imm->handle_event = table_handle_event;

			im->id = strdup(res->d_name);
			*(im->id + (ext - res->d_name)) = 0;
			im->version = strdup("1.0");
			im->methodTable = imm;
			im->imUserData = fullpath;

#if 0
			new_property = (UnihanPropertyArgList)malloc(sizeof(UnihanPropertyArgs));
			memset(new_property, 0, sizeof(UnihanPropertyArgs));
			new_property->type = UNIHAN_PROPERTY_NONE;
			
			im->proplist = new_property;

			new_property = (UnihanPropertyArgList)malloc(sizeof(UnihanPropertyArgs));
			memset(new_property, 0, sizeof(UnihanPropertyArgs));
			new_property->type = UNIHAN_PROPERTY_RUNTIME;
			new_property->UnihanProperty.rt_property.rt_type = UNIHAN_RUNTIME_PROPERTY_FULL_HALF;
			new_property->UnihanProperty.rt_property.value = 1;
			new_property->UnihanProperty.rt_property.name.name_string = "width";
			new_property->UnihanProperty.rt_property.name.charset = "UTF-8";
			new_property->UnihanProperty.rt_property.name.language = "zh_CN";
			new_property->UnihanProperty.rt_property.name.nr_name = 0;
			
			new_property->next = im->proplist;
			im->proplist = new_property;
			
			new_property = (UnihanPropertyArgList)malloc(sizeof(UnihanPropertyArgs));
			memset(new_property, 0, sizeof(UnihanPropertyArgs));
			new_property->type = UNIHAN_PROPERTY_RUNTIME;
			new_property->UnihanProperty.rt_property.rt_type = UNIHAN_RUNTIME_PROPERTY_PUNC_EN_CH;
			new_property->UnihanProperty.rt_property.value = 1;
			new_property->UnihanProperty.rt_property.name.name_string = "puncce";
			new_property->UnihanProperty.rt_property.name.charset = "UTF-8";
			new_property->UnihanProperty.rt_property.name.language = "zh_CN";
			new_property->UnihanProperty.rt_property.name.nr_name = 0;

			new_property->next = im->proplist;
			im->proplist = new_property;
#endif
			im->next = *im_list;
			*im_list = im;
		}
	}
	closedir(dir);
}


static int
table_read_tabfile_header(IMInfo *im, char *fullpath)
{
	FILE *tabfile = NULL;
	size_t len;
	char tmp20[20];

	tabfile = fopen(fullpath, "r");
	if (!tabfile)
		return FALSE;

	len = strlen(TABFILE_SIG)+1;
	if (fread(tmp20, len, 1, tabfile) != 1)
		goto read_header_failed;
	if (strncmp(TABFILE_SIG, tmp20, len-1) != 0)
		goto read_header_failed;

	/* skip ename */
	if (fread(tmp20, 20, 1, tabfile) != 1)
		goto read_header_failed;

	if (fread(tmp20, 20, 1, tabfile) != 1)
		goto read_header_failed;
	im->name = strdup(tmp20);
	
	fclose(tabfile);
	return TRUE;

read_header_failed:
	fclose(tabfile);
	return FALSE;
}


static int
table_init(UnihanIM *uim)
{
	TableDataPerDesktop *dd = NULL;
	long offset = 0;
	size_t length;
	FILE *tabfile = NULL;
	FILE *frqfile = NULL;
	int tabfilefd, frqfilefd;
	int keyListLen, valListLen;
	struct stat tabfilestat;
	struct stat frqfilestat;
	char *pp;
	char frqfilename[256];
	int frq = FALSE;
	
	uim->m->log(uim, UNIHAN_LOG_LEVEL_DEBUG, "table_init()");

	dd = (TableDataPerDesktop *)malloc(sizeof(TableDataPerDesktop));
	if (!dd) {
		goto table_init_failed;
	}
	memset(dd, 0, sizeof(TableDataPerDesktop));

	//get pinyin.frq
	memset(frqfilename, 0, sizeof(frqfile));
	if ((pp = strstr((char *)uim->imUserData, "pinyin")) != NULL) {
		confirm_user_data(uim);
		sprintf(frqfilename, "%s/%s/%s",
				get_home_dir(),
				".unihan2",
				"pinyin.frq");
		frq = TRUE;
		pp = NULL;
	}

	if ((pp = strstr((char *)uim->imUserData, "wubi")) != NULL) {
		confirm_user_data(uim);
		sprintf(frqfilename, "%s/%s/%s",
				get_home_dir(),
				".unihan2",
				"wubizixing.frq");
		frq = TRUE;
		pp = NULL;
	}

	// OK, read the whole fucking table in
	// fullpath should be saved in uim->imUserData
	// actually we only use mmap, currently all platforms we're
	// targetting supports mmap

	// FIXME: check the validity of the file
	tabfilefd = open((char *)uim->imUserData, O_RDONLY, S_IRUSR);
	if (tabfilefd == -1) {
		goto table_init_failed;
	}

	if (frq) {
		frqfilefd = open(frqfilename, O_RDWR, S_IRUSR | S_IWUSR);
		if (frqfilefd == -1) {
			//goto table_init_failed;
			frq = 0;
			dd->FreqMode = 0;
		}
	}

	tabfile = fdopen(tabfilefd, "r");
	if (!tabfile) {
		goto table_init_failed;
	}
	if (frq) {
		frqfile = fdopen(frqfilefd, "r+");
		if (!frqfile) {
			//goto table_init_failed;
			frq = 0;
			dd->FreqMode = 0;
		}
	}

	fstat(tabfilefd, &tabfilestat);
	dd->tabFileSize = tabfilestat.st_size;
	dd->tabFileMMap = mmap(NULL, dd->tabFileSize,
			       PROT_READ, MAP_SHARED, tabfilefd, 0);
	if (dd->tabFileMMap == MAP_FAILED) {
		goto table_init_failed;
	}

	if (frq) {
		fstat(frqfilefd, &frqfilestat);
		dd->frqFileSize = frqfilestat.st_size;
		dd->frqFileMMap = mmap(NULL, dd->frqFileSize,
					PROT_READ | PROT_WRITE, MAP_SHARED, frqfilefd, 0);
		if (dd->frqFileMMap == MAP_FAILED) {
			//goto table_init_failed;
			dd->FreqMode = 0;
		}
	}

	offset += strlen(TABFILE_SIG)+1;
	offset += 20; // ename
	offset += 20; // cname

	fseek(tabfile, offset, SEEK_SET);
	fread(&dd->hints, sizeof(unsigned long), 1, tabfile);
	fread(&dd->preeditMax, sizeof(int), 1, tabfile);
	fread(&dd->numKey, sizeof(int), 1, tabfile);
	fread(&dd->numVal, sizeof(int), 1, tabfile);
	fread(&keyListLen, sizeof(int), 1, tabfile);
	fread(&valListLen, sizeof(int), 1, tabfile);

	offset = ftell(tabfile);

	length = sizeof(KeyName) * NUM_KEYS;
	dd->keyName = (KeyName *)(dd->tabFileMMap + offset);
	offset += length;

	length = sizeof(FastIdx) * NUM_KEYS;
	dd->fastIdx = (FastIdx *)(dd->tabFileMMap + offset);
	offset += length;

	length = sizeof(KeyValTab) * dd->numVal;
	dd->kvtab = (KeyValTab *)(dd->tabFileMMap + offset);
	offset += length;

	length = sizeof(char) * keyListLen;
	dd->keyList = (char *)(dd->tabFileMMap + offset);
	offset += length;

	length = sizeof(char) * valListLen;
	dd->valList = (char *)(dd->tabFileMMap + offset);
	offset += length;

	dd->sp_table = (SPTable *)malloc(sizeof(SPTable));
	sp_table_init(dd->sp_table);

	if (frq) {
		int *num_max = NULL;
		int i;
		num_max = (int *)dd->frqFileMMap;
		dd->maxFrqList = num_max + 1;
		if (num_max)
			dd->frqList = (int *)dd->frqFileMMap + 1 + *num_max;
	}
	uim->desktopUserData = dd;

	dd->PYMode = PINYIN;
	//dd->IllMode = 0;

	return TRUE;

table_init_failed:
	if (tabfile) fclose(tabfile);
	if (frq)
		if (frqfile) fclose(frqfile);
	if (dd) {
		free(dd);
	}
	return FALSE;
}


static int
table_done(UnihanIM *uim)
{
	uim->m->log(uim, UNIHAN_LOG_LEVEL_DEBUG, "table_done()");

	if (uim->desktopUserData) {
		TableDataPerDesktop *dd = (TableDataPerDesktop *)uim->desktopUserData;
		if (dd->sp_table)
			free(dd->sp_table);
		munmap(dd->tabFileMMap, dd->tabFileSize);
		if (dd->FreqMode)
			munmap(dd->frqFileMMap, dd->frqFileSize);
		free(uim->desktopUserData);
		uim->desktopUserData = NULL;
	}

	return TRUE;
}


static int
table_create_session(UnihanIM *uim)
{
	TableDataPerSession *ds = NULL;

	uim->m->log(uim, UNIHAN_LOG_LEVEL_DEBUG, "table_create_session()");

	ds = (TableDataPerSession *)malloc(sizeof(TableDataPerSession));
	if (!ds) {
		goto table_create_session_failed;
	}
	memset(ds, 0, sizeof(TableDataPerSession));

	ds->handle_key_event = handle_key_event_idle;
	//ds->frqCandList = NULL;
	ds->frqAppend = 3;

	ds->candFactory = (CandFactory *)malloc(sizeof(CandFactory));
	memset(ds->candFactory, 0, sizeof(CandFactory));

	uim->sessionUserData = ds;
	return TRUE;

table_create_session_failed:
	return FALSE;
}


static int
table_destroy_session(UnihanIM *uim)
{
	TableDataPerSession *ds = (TableDataPerSession *)uim->sessionUserData;
	TableDataPerDesktop *dd = (TableDataPerDesktop *)uim->desktopUserData;

	uim->m->log(uim, UNIHAN_LOG_LEVEL_DEBUG, "table_destroy_session()");

        lookup_done(uim);
        preedit_erase(uim);

	if (ds->cands)
		free(ds->cands);

	if (uim->sessionUserData) {
		if (ds->frqCandList) {
			free(ds->frqCandList);
			ds->frqCandList = NULL;
		}
		if (dd->PYMode == SHUANGPIN)
			sp_clear_InputCell(uim);
		if (ds->candFactory)
			free(ds->candFactory);
		ds->candFactory = NULL;
		free(uim->sessionUserData);
	}

	return TRUE;
}

static int
table_handle_event(UnihanIM *uim, IMEvent *ev)
{
	TableDataPerSession *ds = (TableDataPerSession *)uim->sessionUserData;
	TableDataPerDesktop *dd = (TableDataPerDesktop *)uim->desktopUserData;
	int ret;

    char *evtype = "unknown";
    switch (ev->type) {
        case IM_KEY:               evtype = "IM_KEY"; break;
        case IM_SWITCH:            evtype = "IM_SWITCH"; break;
        case IM_FOCUS:             evtype = "IM_FOCUS"; break;
        case IM_CONFIG_CHANGE:     evtype = "IM_CONFIG_CHANGE"; break;
        case IM_PROPERTY_TOGGLED:  evtype = "IM_PROPERTY_TOGGLED"; break;
        case IM_PROPERTY_REGISTER: evtype = "IM_PROPERTY_REGISTER"; break;
        case IM_LOOKUP_PAGE_TURN:  evtype = "IM_LOOKUP_PAGE_TURN"; break;
        case IM_LOOKUP_SELECT:     evtype = "IM_LOOKUP_SELECT"; break;
        case IM_NOTHING:           evtype = "IM_NOTHING"; break;
    }
	uim->m->log(uim, UNIHAN_LOG_LEVEL_DEBUG, "table_handle_event(), %s (%d)", evtype, ev->type);

	switch (ev->type) {
		case IM_KEY: {
			ret = ds->handle_key_event(uim, (IMKeyEvent *)ev);
			if (ret == TRUE ||
				!(ds->handle_key_event == handle_key_event_idle)) {
			    //!(ds->handle_key_event == handle_key_event_idle && (dd->is_full || dd->is_CH))) {
				return ret;
			}
			return handle_key_event_idle_punctuation(uim, (IMKeyEvent *)ev);
		}
		case IM_SWITCH:
			ds->handle_key_event = handle_key_event_idle;
			lookup_done(uim);
			preedit_erase(uim);
			return TRUE;

        case IM_FOCUS:
            return handle_focus_event(uim, (IMFocusEvent *)ev);

        case IM_CONFIG_CHANGE:
            return handle_config_change_event(uim,
									(IMConfigChangeEvent *)ev);
        
#if NEW_PROPERTY
		case IM_PROPERTY_TOGGLED:
			return handle_property_toggled_event(uim, (IMPropertyToggledEvent *)ev);
		case IM_PROPERTY_REGISTER:
			return handle_property_register_event(uim, (IMPropertyRegisterEvent *)ev);
#endif                

#if 0
		case IM_SYMBOL_USER_TABLE:
			return handle_symbol_user_table(uim,
					(IMUserTableEvent *)ev);
		case IM_WORD_USER_TABLE:
			return handle_word_user_table(uim,
					(IMUserTableEvent *)ev);
#endif
        case IM_LOOKUP_PAGE_TURN:
        {
            // FIXME: this is totally wrong, we shouldn't abuse the key handling like this
            IMKeyEvent kev;
            kev.type = IM_KEY;
            kev.keyChar = 0;
            kev.modifier = 0;
            kev.keyCode = (ev->lookupPageTurn.direction < 0)? IM_VK_PAGE_UP : IM_VK_PAGE_DOWN;
            return table_handle_event(uim, (IMEvent *)&kev);
        }

        case IM_LOOKUP_SELECT:
        {
            if (ev->lookupSelect.num < 0 || ev->lookupSelect.num > 8)
                return FALSE;

            IMKeyEvent kev;
            kev.type = IM_KEY;
            kev.modifier = 0;
            kev.keyChar = ev->lookupSelect.num + '1';
            kev.keyCode = ev->lookupSelect.num + IM_VK_1;
            return table_handle_event(uim, (IMEvent *)&kev);
        }

        case IM_NOTHING:
                return TRUE;
	}

	return FALSE;
}

static int
handle_focus_event(UnihanIM *uim, IMFocusEvent *fev)
{
	TableDataPerDesktop *dd = (TableDataPerDesktop *)uim->desktopUserData;
	TableDataPerSession *ds = (TableDataPerSession *)uim->sessionUserData;

        if (fev->focused) {
                if (ds->numCands > 0 &&
                    (ds->handle_key_event == handle_key_event_preed_sel ||
                     ds->handle_key_event == handle_key_event_commiting ||
                     ds->handle_key_event == handle_key_event_selecting)) {
			if (dd->IllMode)
				Ill_lookup_draw(uim);
			else
	                        lookup_draw(uim);
		}
                else
			lookup_done(uim);

                if (ds->preeditLen > 0)
                        preedit_draw(uim);
                else
                        uim->m->preedit_erase(uim);
        } else {
                lookup_done(uim);
                uim->m->preedit_erase(uim);
        }
        
        return TRUE;
}

static int
handle_config_change_event(UnihanIM *uim, IMConfigChangeEvent *ev)
{
	TableDataPerDesktop *dd = (TableDataPerDesktop *)uim->desktopUserData;
	char *value;
	long len;

	uim->m->config_get(uim, ev->key, &value, &len);

#if NEW_PROPERTY
#else
	if (!strcasecmp(ev->key, "width")) {

		//dd->is_full = ev->value;

		dd->is_full = uim->m->is_fullwidth(uim);

	} else if (!strcasecmp(ev->key, "puncce")) {

		//dd->is_CH = ev->value;

		dd->is_CH = uim->m->is_chinesepunc(uim);

	} else 
#endif
	if (!strcasecmp(ev->key, "freq")) {
		dd->FreqMode = (value[0] == 't') ? 1 : 0;
	} else if (!strcasecmp(ev->key, "shuangpin")) {
		dd->PYMode = (value[0] == 't') ? SHUANGPIN : PINYIN;
	} else if (!strcasecmp(ev->key, "illegibility")) {
		dd->IllMode = (value[0] == 't') ? 1 : 0;
	}
	return TRUE;
}

#if NEW_PROPERTY
static int
handle_property_toggled_event(UnihanIM * uim, IMPropertyToggledEvent * ev)
{
	TableDataPerDesktop *dd = (TableDataPerDesktop *)uim->desktopUserData;
	UnihanPropertyList *new_property;
	
	new_property = (UnihanPropertyList *)malloc(sizeof(UnihanPropertyList));
	memset(new_property, 0, sizeof(UnihanPropertyList));
	new_property->propertiesNum = 1;
	new_property->properties = (UnihanProperty *)malloc(sizeof(UnihanProperty));
	memset(new_property->properties, 0, sizeof(UnihanProperty));

	if(!strcmp(ev->key, "/PropertyRoot/width")) 
	{
		if(dd->is_full)
		{
			dd->is_full = 0;
			
			new_property->properties[0].key = ev->key;
			new_property->properties[0].label = "半";
			new_property->properties[0].icon = NULL;
			new_property->properties[0].tip = "切换半角/全角模式";
			new_property->properties[0].state = UNIHAN_PROPERTY_ACTIVE;
		}else{
			dd->is_full = 1;
			
			new_property->properties[0].key = ev->key;
			new_property->properties[0].label = "全";
			new_property->properties[0].icon = NULL;
			new_property->properties[0].tip = "切换半角/全角模式";
			new_property->properties[0].state = UNIHAN_PROPERTY_ACTIVE;
		}
		
		properties_update(uim, new_property);
		
	}else if(!strcmp(ev->key, "/PropertyRoot/puncce")) 
	{
		if (dd->is_CH) {
			dd->is_CH = 0;
						
			new_property->properties[0].key = ev->key;
			new_property->properties[0].label = ". ";
			new_property->properties[0].icon = NULL;
			new_property->properties[0].tip = "切换中/英文标点模式";
			new_property->properties[0].state = UNIHAN_PROPERTY_ACTIVE;
		}
		else{
			dd->is_CH = 1;
						
			new_property->properties[0].key = ev->key;
			new_property->properties[0].label = "。";
			new_property->properties[0].icon = NULL;
			new_property->properties[0].tip = "切换中/英文标点模式";
			new_property->properties[0].state = UNIHAN_PROPERTY_ACTIVE;
		}
		
		properties_update(uim, new_property);
		
	}else if(!strcmp(ev->key, "/PropertyRoot/state")) 
	{
		if (dd->chinese_mode) {
			
			dd->chinese_mode = 0;			
			new_property->properties[0].key = ev->key;
			new_property->properties[0].label = "英";
			new_property->properties[0].icon = NULL;
			new_property->properties[0].tip = "切换中/英文输入模式";
			new_property->properties[0].state = UNIHAN_PROPERTY_ACTIVE;
		}
		else{
			
			dd->chinese_mode = 1;			
			new_property->properties[0].key = ev->key;
			new_property->properties[0].label = "中";
			new_property->properties[0].icon = NULL;
			new_property->properties[0].tip = "切换中/英文输入模式";
			new_property->properties[0].state = UNIHAN_PROPERTY_ACTIVE;
		}
		properties_update(uim, new_property);
	}
	
	return 0;
}

static int
handle_property_register_event(UnihanIM * uim, IMPropertyRegisterEvent * ev)
{
	TableDataPerDesktop *dd = (TableDataPerDesktop *)uim->desktopUserData;
	UnihanPropertyList *new_property;
	
	new_property = (UnihanPropertyList *)malloc(sizeof(UnihanPropertyList));
	memset(new_property, 0, sizeof(UnihanPropertyList));
	new_property->propertiesNum = 4;
	new_property->properties = (UnihanProperty *)malloc(4 * sizeof(UnihanProperty));
	memset(new_property->properties, 0, 4*sizeof(UnihanProperty));
	
	new_property->properties[0].key = "/PropertyRoot";
	new_property->properties[0].label = NULL;
	new_property->properties[0].icon = NULL;
	new_property->properties[0].tip = NULL;
	new_property->properties[0].state = UNIHAN_PROPERTY_ACTIVE;
	
	dd->chinese_mode = 1;
	new_property->properties[1].key = "/PropertyRoot/state";
	new_property->properties[1].label = "中";
	new_property->properties[1].icon = NULL;
	new_property->properties[1].tip = "切换中/英文输入模式";;
	new_property->properties[1].state = UNIHAN_PROPERTY_ACTIVE;
	
	dd->is_full = 0;
	new_property->properties[2].key = "/PropertyRoot/width";
	new_property->properties[2].label = "半";
	new_property->properties[2].icon = NULL;
	new_property->properties[2].tip = "切换半角/全角模式";;
	new_property->properties[2].state = UNIHAN_PROPERTY_ACTIVE;
	
	dd->is_CH = 1;
	new_property->properties[3].key = "/PropertyRoot/puncce";
	new_property->properties[3].label = "。";
	new_property->properties[3].icon = NULL;
	new_property->properties[3].tip = "切换中/英文标点模式";
	new_property->properties[3].state = UNIHAN_PROPERTY_ACTIVE;
	
	properties_register(uim, new_property, ev->imid);
	
	return 0;
}

#endif



#if 0
static int
handle_symbol_user_table(UnihanIM *uim, IMUserTableEvent *tev)
{
	TableDataPerDesktop *dd = (TableDataPerDesktop *)uim->desktopUserData;
	if (dd->sut)
		free(dd->sut);
	dd->numSut = tev->numItem;
	dd->sut = (SymbolUserTable *)tev->table;
	return TRUE;
}

static int
handle_word_user_table(UnihanIM *uim, IMUserTableEvent *tev)
{
	TableDataPerDesktop *dd = (TableDataPerDesktop *)uim->desktopUserData;
	if (dd->wut)
		free(dd->wut);
	dd->numWut = tev->numItem;
	dd->wut = (WordUserTable *)tev->table;
	return TRUE;
}
#endif

static int
handle_key_event_idle(UnihanIM *uim, IMKeyEvent *kev)
{
	uim->m->log(uim, UNIHAN_LOG_LEVEL_DEBUG, "handle key event idle");

	TableDataPerDesktop *dd = (TableDataPerDesktop *)uim->desktopUserData;
	TableDataPerSession *ds = (TableDataPerSession *)uim->sessionUserData;
	CandFactory *candFactory = ds->candFactory;
	MultiCandPos *multiCandPos = candFactory->multiCandPos;
	char *sym = NULL;
	char *word = NULL;
	char keychars[2];

	
	if (((kev->keyChar < 0x20) || (kev->keyChar > (NUM_KEYS + 0x20))))
		return FALSE;
		
	if ((kev->modifier & (META_MODIFIER + ALT_MODIFIER + ALT_GRAPH_MODIFIER)) != 0)
		return FALSE;
		
#if NEW_PROPERTY
	if(!dd->chinese_mode) {
		keychars[0] = kev->keyChar;
		keychars[1] = 0;
		uim->m->commit_string(uim, keychars);
		return TRUE;
	}
#endif

	if (dd->keyName[idx(kev->keyChar)].name[0] == '\0') {

		/* distinguish two syllabel */
		/* arren 2004.2.16 */
		if (dd->PYMode == PINYIN) {
			switch ((char)kev->keyChar) {
				case ';':
				case '\'':
				case '\\':
				case '`':
				case ',':
				case '.':
				case '/':
					if (ds->preeditLen > 0)
						ds->preedit[ds->preeditLen] = '-';
					else return FALSE;
					break;
				default:
					return FALSE;
			}
		} else {
			/* user definie symbol */
			if ((sym = get_symbol(uim, kev)) != NULL) {
				commit_string(uim, sym);
				return TRUE;
			}
			return FALSE;
		}
	}
	
	
	if (ds->preeditLen >= PREEDIT_LEN_MAX) {
		uim->m->beep(uim);
		return TRUE;
	}


	if (dd->PYMode == SHUANGPIN) {
		sp_proc(uim, kev, dd->sp_table);
	}
	else {
		// alright, concat this keystroke to the end of keylist...
		if (ds->preedit[ds->preeditLen] != '-')
			ds->preedit[ds->preeditLen] = (char)kev->keyChar;

		ds->preedit[ds->preeditLen+1] = 0;
		ds->preeditLen++;


		if (dd->IllMode) {
			char cell[2];
			cell[0] = kev->keyChar;
			cell[1] = 0;
			Ill_refresh_CandFactory(uim, cell);
		}

		// ... and see if new preedit keylist have matches
		if (dd->IllMode)
			Ill_find_candidates(uim);
		find_candidates(uim);
	}

	if (!ds->numCands) {
		if (dd->PYMode != SHUANGPIN) {
			ds->preeditLen--;
			ds->preedit[ds->preeditLen] = 0;
		}
		if (dd->IllMode)
			Ill_find_candidates(uim);
		find_candidates(uim);

		if (ds->preeditLen == 0) {
			ds->handle_key_event = handle_key_event_idle;
			if (dd->PYMode == SHUANGPIN)
				return TRUE;
			else
				return FALSE;
		} else {
			uim->m->beep(uim);
			return TRUE;
		}
	}

	if (dd->FreqMode)
		freq_sort(uim);
	preedit_draw(uim);
	if (dd->hints & SHOW_AS_U_GO) {
		ds->handle_key_event = handle_key_event_preed_sel;
		if (dd->IllMode)
			Ill_lookup_draw(uim);
		lookup_draw(uim);
	} else
		ds->handle_key_event = handle_key_event_preediting;

	return TRUE;
}

static int
handle_key_event_idle_punctuation(UnihanIM *uim, IMKeyEvent *kev)
{
	uim->m->log(uim, UNIHAN_LOG_LEVEL_DEBUG, "handle key event idle punctuation");

	int i = 0;
	char punc0[2];
	TableDataPerDesktop *dd = (TableDataPerDesktop *)uim->desktopUserData;
	TableDataPerSession *ds = (TableDataPerSession *)uim->sessionUserData;

    while (puncList[i].key) {
        if (kev->keyChar == puncList[i].key) {
            if (ds->preeditLen || ds->numCands) {
                if (kev->keyChar == '=' || kev->keyChar == '-' || kev->keyChar == '\''
						|| (kev->keyChar >= '0' && kev->keyChar <= '9')) {
                    break;
                }
            }

            if (!dd->is_full && dd->is_CH) {
                char *cand = NULL;
                if (kev->keyChar == '"' && quotation_marks) {
                    quotation_marks = 0;
                    cand = puncList[i+1].punc1;
                } else if (kev->keyChar == '"' && !quotation_marks) {
                    quotation_marks = 1;
                    cand = puncList[i].punc1;
                } else if (kev->keyChar == '\'' && quotes) {
                    quotes = 0;
                    cand = puncList[i+1].punc1;
                } else if(kev->keyChar == '\'' && !quotes) {
                    quotes = 1;
                    cand = puncList[i].punc1;
                } else {
                    cand = puncList[i].punc1;
                }

                if (cand) {
                    uim->m->commit_string(uim, cand);
                    return TRUE;
                } else {
                    return FALSE;
                }
            } else if (dd->is_full && !dd->is_CH) {
                uim->m->commit_string(uim, puncList[i].punc2);
                return TRUE;
            } else if (dd->is_full && dd->is_CH) {
                if (kev->keyChar == '"' && quotation_marks) {
                    quotation_marks = 0;
                    uim->m->commit_string(uim, puncList[i+1].punc3);
                    return TRUE;
                } else if(kev->keyChar == '"' && !quotation_marks) {
                    quotation_marks = 1;
                    uim->m->commit_string(uim, puncList[i].punc3);
                    return TRUE;
                }

                if (kev->keyChar == '\'' && quotes) {
                    quotes = 0;
                    uim->m->commit_string(uim, puncList[i+1].punc3);
                    return TRUE;
                } else if(kev->keyChar == '\'' && !quotes) {
                    quotes = 1;
                    uim->m->commit_string(uim, puncList[i].punc3);
                    return TRUE;
                }

                uim->m->commit_string(uim, puncList[i].punc3);
                return TRUE;
            } else {
                return FALSE;
                if (kev->keyChar == '<')
                    uim->m->commit_string(uim, "<");
                else {
                    punc0[0] = puncList[i].key;
                    punc0[1] = 0;
                    uim->m->commit_string(uim, punc0);
                }
                return TRUE;
            }
        }
        i++;
    }
	
    return FALSE;
}


static int
handle_key_event_preed_sel(UnihanIM *uim, IMKeyEvent *kev)
{
	uim->m->log(uim, UNIHAN_LOG_LEVEL_DEBUG, "handle key event preed sel");

	TableDataPerDesktop *dd = (TableDataPerDesktop *)uim->desktopUserData;
	TableDataPerSession *ds = (TableDataPerSession *)uim->sessionUserData;
	CandFactory *candFactory = ds->candFactory;
	int valid = FALSE;

	if ((kev->modifier == 0) && (kev->keyCode == IM_VK_BACK_SPACE)) {
		if (dd->IllMode)
			Ill_back_space(uim);
		if (dd->PYMode == SHUANGPIN)
			sp_back_space(uim);
		else {
			--ds->preeditLen;
			ds->preedit[ds->preeditLen] = 0;
		}

		if (ds->preeditLen > 0) {
			preedit_draw(uim);
			if (dd->IllMode)
				Ill_find_candidates(uim);
			find_candidates(uim);
			if (dd->FreqMode)
				freq_sort(uim);
			if (dd->IllMode)
				Ill_lookup_draw(uim);
			else
				lookup_draw(uim);
		} else {
			lookup_done(uim);
			preedit_erase(uim);
			ds->handle_key_event = handle_key_event_idle;
		}
		return TRUE;
	}

	if ((kev->modifier == 0) && (kev->keyCode == IM_VK_ESCAPE)) {
		lookup_done(uim);
		preedit_erase(uim);
		ds->handle_key_event = handle_key_event_idle;
		return TRUE;
	}

	// FIXME: HK uses Enter
	// FIXME: Space would select next page if in HK mode
	if ((kev->modifier == 0) && (kev->keyCode == IM_VK_SPACE)) {
		int idx;
		if (dd->IllMode) {
			idx = Ill_idx_search(uim);
			if (idx < 0) {
				preedit_erase(uim);
				commit_string(uim, candFactory->word);
				lookup_done(uim);
				ds->handle_key_event = handle_key_event_idle;
				return TRUE;
			}
		}
		#if 0
		else {
			if (candFactory->word) {
				preedit_erase(uim);
				commit_string(uim, candFactory->word);
				lookup_done(uim);
				ds->handle_key_event = handle_key_event_idle;
				return TRUE;
			}
			if (dd->FreqMode)
				idx = freq_idx_search(uim);
			else
				idx = ds->lowIdx + ds->luCurrCandOffset;
		}
		#endif
		preedit_erase(uim);
		commit_string(uim, ds->cands[ds->luCurrCandOffset].cand);
		lookup_done(uim);
		ds->handle_key_event = handle_key_event_idle;
		return TRUE;
	}

	if (((kev->keyChar >= 0x20) && (kev->keyChar <= (NUM_KEYS + 0x20))
	     && (dd->keyName[idx(kev->keyChar)].name[0] != '\0')
	     && ((kev->modifier & (META_MODIFIER + ALT_MODIFIER + ALT_GRAPH_MODIFIER)) == 0)
	     && (ds->preeditLen < PREEDIT_LEN_MAX))) {
		int oldOffset = ds->luCurrCandOffset;

		if (dd->PYMode == SHUANGPIN)
			sp_proc(uim, kev, dd->sp_table);
		else {
			if (ds->preedit[ds->preeditLen] != '-')
				ds->preedit[ds->preeditLen] = (char)kev->keyChar;
			ds->preedit[ds->preeditLen+1] = 0;
			ds->preeditLen++;

			if (dd->IllMode) {
				char cell[2];
				cell[0] = kev->keyChar;
				cell[1] = 0;
				Ill_refresh_CandFactory(uim, cell);
			}

			if (dd->IllMode)
				Ill_find_candidates(uim);
			find_candidates(uim);
		}
		
		if (!ds->numCands) {
			if (dd->PYMode != SHUANGPIN) {
				ds->preeditLen--;
				ds->preedit[ds->preeditLen] = 0;
			}
			if (dd->IllMode)
				Ill_find_candidates(uim);
			find_candidates(uim);
			ds->luCurrCandOffset = oldOffset;

			if (ds->preeditLen == 0) {
				ds->handle_key_event = handle_key_event_idle;
				return FALSE;
			}
		} else {
			valid = TRUE;
			goto candidate_found;
		}
	}

	if ((kev->modifier == 0) && (kev->keyChar >= '1') && (kev->keyChar <= '9')) {
		if (dd->IllMode) {
			int idx = Ill_idx_sel(uim, kev);
                        preedit_erase(uim);
			if (idx < 0) {
				commit_string(uim, candFactory->word);
				lookup_done(uim);
				ds->handle_key_event = handle_key_event_idle;
				return TRUE;
			}
			commit_string(uim, dd->valList + dd->kvtab[idx].valOff);
		}
		else {
			int pageStart = (ds->luCurrCandOffset / 9) * 9;
			int realIdx = pageStart + kev->keyChar - '1';
			if (realIdx >= ds->numCands) {
				uim->m->beep(uim);
				return TRUE;
			}
			preedit_erase(uim);
			commit_string(uim, ds->cands[realIdx].cand);
		}
		lookup_done(uim);
		ds->handle_key_event = handle_key_event_idle;
		return TRUE;
	}

	if ((kev->modifier == IM_SHIFT_MASK && kev->keyChar == '>') ||
	    (kev->modifier == 0 && (kev->keyCode == IM_VK_PAGE_DOWN ||
				    kev->keyChar == '+' ||
				    kev->keyChar == '=' ||
					kev->keyChar == '.'	||
				    kev->keyChar == ']'))) {
		if (dd->IllMode) {
			if (!Ill_page_down(uim))
				return TRUE;
		}
		else {
			if (ds->numCands <= 9) {
				uim->m->beep(uim);
				return TRUE;
			}

			ds->luCurrCandOffset += 9 - (ds->luCurrCandOffset % 9);
			if (ds->luCurrCandOffset >= ds->numCands)
				ds->luCurrCandOffset = 0;
		}

		if (dd->IllMode)
			Ill_lookup_draw(uim);
		else
			lookup_draw(uim);
		return TRUE;
	}

	if ((kev->modifier == IM_SHIFT_MASK && kev->keyChar == '<') ||
	    (kev->modifier == 0 && (kev->keyCode == IM_VK_PAGE_UP ||
				    kev->keyChar == '-' ||
				    kev->keyChar == ',' ||
				    kev->keyChar == '['))) {
		if (dd->IllMode) {
			if (!Ill_page_up(uim))
				return TRUE;
		}
		else {
			if (ds->numCands <= 9) {
				uim->m->beep(uim);
				return TRUE;
			}
			ds->luCurrCandOffset -= 9 + (ds->luCurrCandOffset % 9);
			if (ds->luCurrCandOffset < 0) {
				int offset = ds->numCands % 9;
				if (offset == 0) offset = 9;
				ds->luCurrCandOffset = ds->numCands - offset;
			}
		}

		if (dd->IllMode)
			Ill_lookup_draw(uim);
		else
			lookup_draw(uim);
		return TRUE;
	}

        if (kev->modifier == 0 && (kev->keyCode == IM_VK_UP || kev->keyCode == IM_VK_LEFT)) {
		if (dd->IllMode)
			Ill_sel_up(uim);
		else {
	                if (ds->luCurrCandOffset % 9 == 0)
        	                ds->luCurrCandOffset += 8;
                	else
				ds->luCurrCandOffset--;
                	if (ds->luCurrCandOffset >= ds->numCands)
                        	ds->luCurrCandOffset = ds->numCands - 1;
		}

		if (dd->IllMode)
			Ill_lookup_draw(uim);
		else
	                lookup_draw(uim);
                return TRUE;
        }

        if (kev->modifier == 0 && (kev->keyCode == IM_VK_DOWN || kev->keyCode == IM_VK_RIGHT)) {
		if (dd->IllMode)
			Ill_sel_down(uim);
		else {
	                if (ds->luCurrCandOffset % 9 == 8)
        	                ds->luCurrCandOffset -= 8;
        	        else
                	        ds->luCurrCandOffset++;
                	if (ds->luCurrCandOffset >= ds->numCands)
                        	ds->luCurrCandOffset -= ds->luCurrCandOffset % 9;
		}

		if (dd->IllMode)
			Ill_lookup_draw(uim);
		else
	                lookup_draw(uim);
                return TRUE;
        }
	
	if (((kev->keyChar < 0x20) || (kev->keyChar > (NUM_KEYS + 0x20))) ||
	    (dd->keyName[idx(kev->keyChar)].name[0] == '\0') ||
	    ((kev->modifier & (META_MODIFIER + ALT_MODIFIER + ALT_GRAPH_MODIFIER)) != 0) ||
	    (ds->preeditLen >= PREEDIT_LEN_MAX)) {
		if (dd->PYMode == PINYIN) {
			switch ((char)kev->keyChar) {
				case ';':
				case '\'':
				case '\\':
				case '`':
				case ',':
				case '.':
				case '/':
					if (ds->preeditLen > 0)
						ds->preedit[ds->preeditLen] = '-';
					else return FALSE;
					break;
				default:
					uim->m->beep(uim);
					return TRUE;
			}
		}
		else {
			uim->m->beep(uim);
			return TRUE;
		}
	}

candidate_found:
	if (dd->FreqMode)
		freq_sort(uim);
	preedit_draw(uim);
	if (dd->IllMode)
		Ill_lookup_draw(uim);
	else
		lookup_draw(uim);

	return TRUE;
}

static int
handle_key_event_preediting(UnihanIM *uim, IMKeyEvent *kev)
{
	uim->m->log(uim, UNIHAN_LOG_LEVEL_DEBUG, "handle key event preediting");

	TableDataPerDesktop *dd = (TableDataPerDesktop *)uim->desktopUserData;
	TableDataPerSession *ds = (TableDataPerSession *)uim->sessionUserData;
	CandFactory *candFactory = ds->candFactory;

	if ((kev->modifier == 0) && (kev->keyCode == IM_VK_BACK_SPACE)) {
		if (dd->IllMode)
			Ill_back_space(uim);
		if (dd->PYMode == SHUANGPIN)
			sp_back_space(uim);
		else {
			--ds->preeditLen;
			ds->preedit[ds->preeditLen] = 0;
		}

		if (ds->preeditLen > 0) {
			if (dd->IllMode)
				Ill_find_candidates(uim);
			find_candidates(uim);
			if (dd->FreqMode)
				freq_sort(uim);
			preedit_draw(uim);
		} else {
			lookup_done(uim);
			preedit_erase(uim);
			ds->handle_key_event = handle_key_event_idle;
		}
		return TRUE;
	}

	if ((kev->modifier == 0) && (kev->keyCode == IM_VK_ESCAPE)) {
		lookup_done(uim);
		preedit_erase(uim);
		ds->handle_key_event = handle_key_event_idle;
		return TRUE;
	}

	if ((kev->modifier == 0) && (kev->keyCode == IM_VK_SPACE)) {
                if (ds->luCurrCandOffset == 0 && ds->numExactCands == 1) {
			int idx;
			if (dd->IllMode) {
				idx = Ill_idx_search(uim);
				if (idx < 0) {
					preedit_erase(uim);
					commit_string(uim, candFactory->word);
					lookup_done(uim);
					ds->handle_key_event = handle_key_event_idle;
					return TRUE;
				}
			}
			#if 0
			else {
				if (candFactory->word) {
					preedit_erase(uim);
					commit_string(uim, candFactory->word);
					lookup_done(uim);
					ds->handle_key_event = handle_key_event_idle;
					return TRUE;
				}
				if (dd->FreqMode)
					idx = ds->lowIdx + ds->frqCandList[0].id;
				else
					idx = ds->lowIdx;
			}
			#endif
                        preedit_erase(uim);
                        commit_string(uim, ds->cands[0].cand);
                        lookup_done(uim);
                        ds->handle_key_event = handle_key_event_idle;
                } else if (ds->numExactCands > 0) {
                        ds->handle_key_event = handle_key_event_commiting;
			if (dd->IllMode)
				Ill_lookup_draw(uim);
			else
	                        lookup_draw(uim);
                } else {
                        uim->m->beep(uim);
                }
                return TRUE;
	}

	if ((kev->keyChar != '*') && (((kev->keyChar < 0x20) || (kev->keyChar > (NUM_KEYS + 0x20))) ||
	    (dd->keyName[idx(kev->keyChar)].name[0] == '\0') ||
	    (kev->modifier != 0)) ||
	    (ds->preeditLen >= PREEDIT_LEN_MAX)) {
		if (dd->PYMode == PINYIN) {
			switch ((char)kev->keyChar) {
				case ';':
				case '\'':
				case '\\':
				case '`':
				case ',':
				case '.':
				case '/':
					if (ds->preeditLen > 0)
						ds->preedit[ds->preeditLen] = '-';
					else return FALSE;
					break;
				default:
					uim->m->beep(uim);
					return TRUE;
			}
		}
		else {
			uim->m->beep(uim);
			return TRUE;
		}
	}

	if (dd->PYMode == SHUANGPIN)
		sp_proc(uim, kev, dd->sp_table);
	else {
		if (ds->preedit[ds->preeditLen] != '-')
			ds->preedit[ds->preeditLen] = (char)kev->keyChar;
		ds->preedit[ds->preeditLen+1] = 0;
		ds->preeditLen++;

		if (dd->IllMode) {
			char cell[2];
			cell[0] = kev->keyChar;
			cell[1] = 0;
			Ill_refresh_CandFactory(uim, cell);
		}

		if (dd->IllMode)
			Ill_find_candidates(uim);
		find_candidates(uim);
	}
	
	if (!ds->numCands) {
		if (dd->PYMode != SHUANGPIN) {
			ds->preeditLen--;
			ds->preedit[ds->preeditLen] = 0;
		}
		if (dd->IllMode)
			Ill_find_candidates(uim);
		find_candidates(uim);

		if (ds->preeditLen == 0) {
			ds->handle_key_event = handle_key_event_idle;
			return FALSE;
		} else {
			uim->m->beep(uim);
			return TRUE;
		}
	} else {
		if ((dd->hints & SELECT_AFTER_MAX_PREEDIT) && (ds->preeditLen >= dd->preeditMax)) {
			ds->handle_key_event = handle_key_event_commiting;
			lookup_draw(uim);
		}
	}
	if (dd->FreqMode)
		freq_sort(uim);
	preedit_draw(uim);

	return TRUE;
}

static int
handle_key_event_commiting(UnihanIM *uim, IMKeyEvent *kev)
{
	uim->m->log(uim, UNIHAN_LOG_LEVEL_DEBUG, "handle key event commiting");

	TableDataPerDesktop *dd = (TableDataPerDesktop *)uim->desktopUserData;
	TableDataPerSession *ds = (TableDataPerSession *)uim->sessionUserData;
	CandFactory *candFactory = ds->candFactory;

	if ((kev->modifier == 0) && (kev->keyCode == IM_VK_ESCAPE)) {
		lookup_done(uim);
		preedit_erase(uim);
		ds->handle_key_event = handle_key_event_idle;
		return TRUE;
	}

	if ((kev->modifier == 0) && (kev->keyCode == IM_VK_SPACE)) {
                if (ds->numExactCands <= 9) {
                        int idx;
			if (dd->IllMode) {
				idx = Ill_idx_search(uim);
				if (idx < 0) {
					preedit_erase(uim);
					commit_string(uim, candFactory->word);
					lookup_done(uim);
					ds->handle_key_event = handle_key_event_idle;
					return TRUE;
				}
			}
			#if 0
			else {
				if (candFactory->word) {
					preedit_erase(uim);
					commit_string(uim, candFactory->word);
					lookup_done(uim);
					ds->handle_key_event = handle_key_event_idle;
					return TRUE;
				}
				if (dd->FreqMode)
					idx = freq_idx_search(uim);
				else
					idx = ds->lowIdx + ds->luCurrCandOffset;
			}
			#endif
                        preedit_erase(uim);
                        commit_string(uim, ds->cands[ds->luCurrCandOffset].cand);
                        lookup_done(uim);
                        ds->handle_key_event = handle_key_event_idle;
                        return TRUE;
                }
                // more than 9 candidates? then Space means next page...
                ds->handle_key_event = handle_key_event_selecting;
                return handle_key_event_selecting(uim, kev);
	}

	if ((kev->modifier == 0) && (kev->keyChar >= '1') && (kev->keyChar <= '9')) {
		if (dd->IllMode) {
			int idx = Ill_idx_sel(uim, kev);
                        preedit_erase(uim);
			if (idx < 0) {
				commit_string(uim, candFactory->word);
				lookup_done(uim);
				ds->handle_key_event = handle_key_event_idle;
				return TRUE;
			}
			commit_string(uim, dd->valList + dd->kvtab[idx].valOff);
		}
		else {
			int pageStart = (ds->luCurrCandOffset / 9) * 9;
			int realIdx = pageStart + kev->keyChar - '1';
			if (realIdx >= ds->numCands) {
				uim->m->beep(uim);
				return TRUE;
			}
			preedit_erase(uim);
			commit_string(uim, ds->cands[realIdx].cand);
		}
		lookup_done(uim);
		ds->handle_key_event = handle_key_event_idle;
		return TRUE;
	}

	if ((kev->modifier == IM_SHIFT_MASK && kev->keyChar == '>') ||
	    (kev->modifier == 0 && (kev->keyCode == IM_VK_PAGE_DOWN ||
				    kev->keyChar == '+' ||
				    kev->keyChar == '=' ||
				    kev->keyChar == '.' ||
				    kev->keyChar == ']'))) {
		if (dd->IllMode) {
			if (!Ill_page_down(uim))
				return TRUE;
		}
		else {
			if (ds->numCands <= 9) {
				uim->m->beep(uim);
				return TRUE;
			}
			ds->luCurrCandOffset += 9 - (ds->luCurrCandOffset % 9);
			if (ds->luCurrCandOffset >= ds->numCands)
				ds->luCurrCandOffset = 0;
		}

		if (dd->IllMode)
			Ill_lookup_draw(uim);
		else
			lookup_draw(uim);
		return TRUE;
	}

	if ((kev->modifier == IM_SHIFT_MASK && kev->keyChar == '<') ||
	    (kev->modifier == 0 && (kev->keyCode == IM_VK_PAGE_UP ||
				    kev->keyChar == '-' ||
				    kev->keyChar == ',' ||
				    kev->keyChar == '['))) {
		if (dd->IllMode) {
			if (!Ill_page_up(uim))
				return TRUE;
		}
		else {
			if (ds->numCands <= 9) {
				uim->m->beep(uim);
				return TRUE;
			}
			ds->luCurrCandOffset -= 9 + (ds->luCurrCandOffset % 9);
			if (ds->luCurrCandOffset < 0) {
				int offset = ds->numCands % 9;
				if (offset == 0) offset = 9;
				ds->luCurrCandOffset = ds->numCands - offset;
			}
		}

		if (dd->IllMode)
			Ill_lookup_draw(uim);
		else
			lookup_draw(uim);
		return TRUE;
	}

        if (kev->modifier == 0 && (kev->keyCode == IM_VK_UP || kev->keyCode == IM_VK_LEFT)) {
		if (dd->IllMode)
			Ill_sel_up(uim);
		else {
	                if (ds->luCurrCandOffset % 9 == 0)
        	                ds->luCurrCandOffset += 8;
        	        else
                	        ds->luCurrCandOffset--;
                	if (ds->luCurrCandOffset >= ds->numCands)
                        	ds->luCurrCandOffset = ds->numCands - 1;
		}

		if (dd->IllMode)
			Ill_lookup_draw(uim);
		else
	                lookup_draw(uim);
                return TRUE;
        }

        if (kev->modifier == 0 && (kev->keyCode == IM_VK_DOWN || kev->keyCode == IM_VK_RIGHT)) {
		if (dd->IllMode)
			Ill_sel_down(uim);
		else {
	                if (ds->luCurrCandOffset % 9 == 8)
        	                ds->luCurrCandOffset -= 8;
                	else
                        	ds->luCurrCandOffset++;
                	if (ds->luCurrCandOffset >= ds->numCands)
                        	ds->luCurrCandOffset -= ds->luCurrCandOffset % 9;
		}

		if (dd->IllMode)
			Ill_lookup_draw(uim);
		else
	                lookup_draw(uim);
                return TRUE;
        }

	if (((kev->keyChar < 0x20) || (kev->keyChar > (NUM_KEYS + 0x20))) ||
	    (dd->keyName[idx(kev->keyChar)].name[0] == '\0') ||
	    (kev->modifier != 0) ||
	    (ds->preeditLen >= PREEDIT_LEN_MAX)) {
		uim->m->beep(uim);
		return TRUE;
	}

        // ok, this is a valid key for composing the next preedit
        // therefore, commit the 1st choice on the current lookup window,
        // close the window and pass on to handle_preediting
        preedit_erase(uim);
	int pageStart = (ds->luCurrCandOffset/9)*9;
	if (dd->IllMode) {
		int idx = Ill_idx_search(uim);
		if (idx < 0) {
			commit_string(uim, candFactory->word);
			lookup_done(uim);
			ds->handle_key_event = handle_key_event_preediting;
			return handle_key_event_preediting(uim, kev);
		}
		commit_string(uim, ds->cands[pageStart].cand);
	}
	else {
		commit_string(uim, ds->cands[pageStart].cand);
	}
        lookup_done(uim);
        ds->handle_key_event = handle_key_event_preediting;
        return handle_key_event_preediting(uim, kev);
}

static int
handle_key_event_selecting(UnihanIM *uim, IMKeyEvent *kev)
{
	uim->m->log(uim, UNIHAN_LOG_LEVEL_DEBUG, "handle key event selecting");

	TableDataPerDesktop *dd = (TableDataPerDesktop *)uim->desktopUserData;
	TableDataPerSession *ds = (TableDataPerSession *)uim->sessionUserData;
	CandFactory *candFactory = ds->candFactory;

	if ((kev->modifier == 0) && (kev->keyCode == IM_VK_ESCAPE)) {
		lookup_done(uim);
		preedit_erase(uim);
		ds->handle_key_event = handle_key_event_idle;
		return TRUE;
	}

	if ((kev->modifier == 0) && (kev->keyChar >= '1') && (kev->keyChar <= '9')) {
		if (dd->IllMode) {
			int idx = Ill_idx_sel(uim, kev);
                        preedit_erase(uim);
			if (idx < 0) {
				commit_string(uim, candFactory->word);
				lookup_done(uim);
				ds->handle_key_event = handle_key_event_idle;
				return TRUE;
			}
			commit_string(uim, dd->valList + dd->kvtab[idx].valOff);
		}
		else {
			int pageStart = (ds->luCurrCandOffset / 9) * 9;
			int realIdx = pageStart + kev->keyChar - '1';
			if (realIdx >= ds->numCands) {
				uim->m->beep(uim);
				return TRUE;
			}
			preedit_erase(uim);
			commit_string(uim, ds->cands[realIdx].cand);
		}
		lookup_done(uim);
		ds->handle_key_event = handle_key_event_idle;
		return TRUE;
	}

	if ((kev->modifier == 0 && kev->keyCode == IM_VK_SPACE) ||
            (kev->modifier == IM_SHIFT_MASK && kev->keyChar == '>') ||
	    (kev->modifier == 0 && (kev->keyCode == IM_VK_PAGE_DOWN ||
				    kev->keyChar == '+' ||
				    kev->keyChar == '=' ||
				    kev->keyChar == '.' ||
				    kev->keyChar == ']'))) {
		if (dd->IllMode) {
			if (!Ill_page_down(uim))
				return TRUE;
		}
		else {
			if (ds->numCands <= 9) {
				uim->m->beep(uim);
				return TRUE;
			}
			ds->luCurrCandOffset += 9 - (ds->luCurrCandOffset % 9);
			if (ds->luCurrCandOffset >= ds->numCands)
				ds->luCurrCandOffset = 0;
		}

		if (dd->IllMode)
			Ill_lookup_draw(uim);
		else
			lookup_draw(uim);
		return TRUE;
	}

	if ((kev->modifier == IM_SHIFT_MASK && kev->keyChar == '<') ||
	    (kev->modifier == 0 && (kev->keyCode == IM_VK_PAGE_UP ||
				    kev->keyChar == '-' ||
				    kev->keyChar == ',' ||
				    kev->keyChar == '['))) {
		if (dd->IllMode) {
			if (!Ill_page_up(uim))
				return TRUE;
		}
		else {
			if (ds->numCands <= 9) {
				uim->m->beep(uim);
				return TRUE;
			}
			ds->luCurrCandOffset -= 9 + (ds->luCurrCandOffset % 9);
			if (ds->luCurrCandOffset < 0) {
				int offset = ds->numCands % 9;
				if (offset == 0) offset = 9;
				ds->luCurrCandOffset = ds->numCands - offset;
			}
		}

		if (dd->IllMode)
			Ill_lookup_draw(uim);
		else
			lookup_draw(uim);
		return TRUE;
	}

        if (kev->modifier == 0 && (kev->keyCode == IM_VK_UP || kev->keyCode == IM_VK_LEFT)) {
		if (dd->IllMode)
			Ill_sel_up(uim);
		else {
	                if (ds->luCurrCandOffset % 9 == 0)
        	                ds->luCurrCandOffset += 8;
        	        else
                	        ds->luCurrCandOffset--;
            	    if (ds->luCurrCandOffset >= ds->numCands)
                	        ds->luCurrCandOffset = ds->numCands - 1;
		}

		if (dd->IllMode)
			Ill_lookup_draw(uim);
		else
	                lookup_draw(uim);
                return TRUE;
        }

        if (kev->modifier == 0 && (kev->keyCode == IM_VK_DOWN || kev->keyCode == IM_VK_RIGHT)) {
		if (dd->IllMode)
			Ill_sel_right(uim);
		else {
	                if (ds->luCurrCandOffset % 9 == 8)
        	                ds->luCurrCandOffset -= 8;
                	else
                        	ds->luCurrCandOffset++;
	                if (ds->luCurrCandOffset >= ds->numCands)
        	                ds->luCurrCandOffset -= ds->luCurrCandOffset % 9;
		}

		if (dd->IllMode)
			Ill_lookup_draw(uim);
		else
	                lookup_draw(uim);
                return TRUE;
        }

        // oops, any other key is invalid
        uim->m->beep(uim);
	return TRUE;
}

static int
is_valid_code(UnihanIM *uim, char *preedit, int preeditLen)
{
	TableDataPerDesktop *dd = (TableDataPerDesktop *)uim->desktopUserData;
	TableDataPerSession *ds = (TableDataPerSession *)uim->sessionUserData;
	int low, high, i;

        if (preeditLen == 0)
		return FALSE;
	low = dd->fastIdx[idx(preedit[0])].offset;
	high = dd->fastIdx[idx(preedit[0])+1].offset - 1;
        i = (low + high)/2;

	// binary search
	while (high >= low) {
		int cmp;
		KeyValTab *kv = NULL;
		
		i = (low + high)/2;

		kv = &(dd->kvtab[i]);
		cmp = strncmp(preedit, dd->keyList+(kv->keyOff), preeditLen);
		if (cmp < 0)
			high = i - 1;
		else if (cmp > 0)
			low = i + 1;
		else // cmp == 0!!! found!
			return TRUE;
	}
	return FALSE;
}

static int 
compare_candidate_order(const void *a1, const void *a2)
{
	Candidate *c1 = (Candidate*)a1;
	Candidate *c2 = (Candidate*)a2;
	return c1->order - c2->order;
}

void
find_candidates(UnihanIM *uim)
{
	TableDataPerDesktop *dd = (TableDataPerDesktop *)uim->desktopUserData;
	TableDataPerSession *ds = (TableDataPerSession *)uim->sessionUserData;
	CandFactory *candFactory = ds->candFactory;
	int low, high, exactIdx, i;
	KeyValTab *kv = NULL;

        if (ds->preeditLen == 0)
                // what the fuck?!
		// yes. I try finding candidates when preeditLen == 0 . Arren.
                return;
	low = dd->fastIdx[idx(ds->preedit[0])].offset;
	high = dd->fastIdx[idx(ds->preedit[0])+1].offset - 1;
        i = (low + high)/2;

	// forget about binary search if handling "?*?"
	if ((ds->preeditLen == 2) && (ds->preedit[1] == '*')) {
		ds->numCands = high - low;
		if (ds->cands)
			free(ds->cands);
		ds->cands = (Candidate*)malloc(sizeof(Candidate) * (ds->numCands));
		memset(ds->cands, 0, sizeof(Candidate)*(ds->numCands));
		int j=0;
		for (i = 0; i < ds->numCands; ++i, ++j) {
			int index;
			if (dd->FreqMode)
				index = ds->lowIdx + ds->frqCandList[i].id;
			else
				index = ds->lowIdx + i;
			ds->cands[j].cand = dd->valList + dd->kvtab[index].valOff;
			ds->cands[j].key = "";
			ds->cands[j].order = strlen(dd->keyList + dd->kvtab[index].keyOff);
		}
		qsort(ds->cands, ds->numCands, sizeof(Candidate), compare_candidate_order);
		ds->numExactCands = ds->numCands;
	} else if ((ds->preeditLen == 3) && (ds->preedit[1] == '*')) {
		int numCands = 0;
		if (ds->cands)
			free(ds->cands);
		ds->cands = (Candidate*)malloc(sizeof(Candidate) * (ds->numCands));
		memset(ds->cands, 0, sizeof(Candidate)*(ds->numCands));
		for (i = low; i < high; ++i) {
			kv = &(dd->kvtab[i]);
			char *key = dd->keyList+(kv->keyOff);
			if (ds->preedit[2] == key[strlen(key)-1]) {
				ds->cands[numCands].cand = dd->valList + dd->kvtab[i].valOff;
				ds->cands[numCands].key = "";
				ds->cands[numCands].order = strlen(dd->keyList + dd->kvtab[i].keyOff);
				++numCands;
			}
		}
		qsort(ds->cands, numCands, sizeof(Candidate), compare_candidate_order);
		ds->numCands = numCands;
		ds->numExactCands = numCands;
	} else {
	// binary search
        if (ds->preeditLen != 1) {
                while (ds->preeditLen > 1) {
                        int cmp;
		
                        if (high < low) {
                                ds->numExactCands = ds->highIdx = ds->lowIdx = ds->exactIdx = -1;
                                ds->numCands = 0;
                                return;
                        }

                        i = (low + high)/2;

                        kv = &(dd->kvtab[i]);
                        cmp = strncmp(ds->preedit + 1, dd->keyList+(kv->keyOff) + 1, ds->preeditLen-1);
                        if (cmp < 0)
                                high = i - 1;
                        else if (cmp > 0)
                                low = i + 1;
                        else // cmp == 0!!! found!
                                break;
                }
        }
	// find the real upper and lower ranges
	low = i - 1;
	high = i + 1;
	while (low >= 0) {
		KeyValTab *kv1 = &(dd->kvtab[low]);
		int cmp = strncmp(ds->preedit, dd->keyList+(kv1->keyOff), ds->preeditLen);
		if (cmp != 0) // found start of range
			break;
		--low;
	}
	++low;
	while (high < dd->numVal) {
		KeyValTab *kv1 = &(dd->kvtab[high]);
		int cmp = strncmp(ds->preedit, dd->keyList+(kv1->keyOff), ds->preeditLen);
		if (cmp != 0)
			break;
		++high;
	}
	// find how many are exact matches
	kv = &(dd->kvtab[low]);
	if (ds->preeditLen < strlen(dd->keyList + kv->keyOff)) {
		exactIdx = low + 1;
	} else {
		exactIdx = low + 1;
		while (exactIdx < high) {
			kv = &(dd->kvtab[exactIdx]);
			if (*(dd->keyList+kv->keyOff+ds->preeditLen) != 0)
				break;
			++exactIdx;
		}
	}

	ds->lowIdx = low;
	ds->highIdx = high;
	ds->exactIdx = exactIdx;
	ds->numExactCands = exactIdx - low;

	if (dd->hints & SHOW_AS_U_GO)
		ds->numCands = high - low;
	else
		ds->numCands = ds->numExactCands;

	if (ds->cands)
		free(ds->cands);
	ds->cands = (Candidate*)malloc(sizeof(Candidate) * (ds->numCands+1));
	memset(ds->cands, 0, sizeof(Candidate)*(ds->numCands+1));

	int j = 0;
	candFactory->word = get_word(uim);
	if (candFactory->word) {
		ds->cands[0].cand = candFactory->word;
		ds->cands[0].key = "";
		ds->numCands++;
		++j;
	}

	for (i = 0; i < ds->numCands; ++i, ++j) {
		int index;
		if (dd->FreqMode)
			index = ds->lowIdx + ds->frqCandList[i].id;
		else
			index = ds->lowIdx + i;
		ds->cands[j].cand = dd->valList + dd->kvtab[index].valOff;
		ds->cands[j].key = dd->keyList + dd->kvtab[index].keyOff;
	}
	}

	ds->luCurrCandOffset = 0;
}

static void
lookup_draw(UnihanIM *uim)
{
	TableDataPerDesktop *dd = (TableDataPerDesktop *)uim->desktopUserData;
	TableDataPerSession *ds = (TableDataPerSession *)uim->sessionUserData;
	CandFactory *candFactory = ds->candFactory;
	UnihanLookupDraw ludraw;
	int numCands = 0;
	int i,j;

	if (ds->lookupShown == 0) {
		UnihanLookupStart lustart;
		lustart.choicePerWindow = 9;
		lustart.numColumns = 9;
		lustart.numRows = 1;
		lustart.direction = UNIHAN_LOOKUP_DIRECTION_RIGHT;
		uim->m->lookup_start(uim, &lustart);
		ds->lookupShown = 1;
	}

	ludraw.mode = UNIHAN_LOOKUP_MODE_NORMAL;
	if ((ds->luCurrCandOffset / 9) * 9 + 9 >= ds->numCands) {
		ludraw.numChoices = ds->numCands - (ds->luCurrCandOffset / 9) * 9;
	}
	else
		ludraw.numChoices = 9;
	ludraw.selected = ds->luCurrCandOffset % 9;
        ludraw.currentChoice = ds->luCurrCandOffset; 
        ludraw.totalChoices = ds->numCands;
	ludraw.title = "";
	uim->m->mem_alloc(uim, sizeof(char *) * ludraw.numChoices, (void**)&ludraw.label);
	uim->m->mem_alloc(uim, sizeof(char *) * ludraw.numChoices, (void**)&ludraw.value);

	for (i = numCands, j=0; i < ludraw.numChoices; ++i, ++j) {
		int index;
                UnihanTCSCMode tcsc;
                char *cand;
                char *key;
                char *pehint = NULL;
                char *converted = NULL;
                int len = 0;
		
		uim->m->mem_alloc(uim, sizeof(char)*2, (void**)&(ludraw.label[i]));
		ludraw.label[i][0] = i + '1';
		ludraw.label[i][1] = 0;

#if 0
		if (dd->FreqMode)
			index = ds->lowIdx + ds->frqCandList[((ds->luCurrCandOffset / 9) * 9) +
				j].id;
		else 
			index = ds->lowIdx + ((ds->luCurrCandOffset/9)*9) + j;
		
                cand = dd->valList + dd->kvtab[index].valOff;
		
                key = dd->keyList + dd->kvtab[index].keyOff;
#endif
		cand = ds->cands[((ds->luCurrCandOffset/9)*9) + j].cand;
		key = ds->cands[((ds->luCurrCandOffset/9)*9) + j].key;

                len = strlen(cand);

                if (ds->preeditLen < strlen(key)) {
                        pehint = key + ds->preeditLen;
                        len += strlen(pehint) + 1;
                }

                uim->m->tcsc_mode_get(uim, &tcsc);
                if (tcsc != UNIHAN_TCSC_NO_CONV) {
                        uim->m->tcsc_do_conv(uim, tcsc, cand, &converted);

                        if (!strcmp(converted, cand)) {
                                converted = NULL;
                        } else {
                                len += strlen(converted) + 5; // for the arrow
                        }
                }

                uim->m->mem_alloc(uim, len + 1, (void**)&(ludraw.value[i]));
                memset(ludraw.value[i], 0, len + 1);
                if (pehint) {
                        strcat(ludraw.value[i], pehint);
                        strcat(ludraw.value[i], " ");
                }
                strcat(ludraw.value[i], cand);
                if (converted) {
                        strcat(ludraw.value[i], " \342\206\222 "); // U+2192 right arrow
                        strcat(ludraw.value[i], converted);
                }
	}
	ludraw.labelStyles = NULL;
	ludraw.valueStyles = NULL;
	
	uim->m->lookup_draw(uim, &ludraw);
	return;
}


static void
lookup_done(UnihanIM *uim)
{
	TableDataPerSession *ds = (TableDataPerSession *)uim->sessionUserData;

	if (ds->lookupShown != 0) {
		uim->m->lookup_done(uim);
		ds->lookupShown = 0;
	}
}


static void
preedit_draw(UnihanIM *uim)
{
	TableDataPerDesktop *dd = (TableDataPerDesktop *)uim->desktopUserData;
	TableDataPerSession *ds = (TableDataPerSession *)uim->sessionUserData;
        char *c = ds->preedit;
        char *peString = NULL, *p = NULL;
        int len = 0;

        // see how long the PE string would be in UTF-8
        while (*c) {
                len += strlen(dd->keyName[idx(*c)].name);
                ++c;
        }
        uim->m->mem_alloc(uim, len + 1, (void**)&peString);
        memset(peString, 0, len + 1);
        
        // is this inefficient?
        // complexity O(2n), but what the fuck.. ds->preedit is a few bytes
        // long only anyway
        c = ds->preedit;
        p = peString;
        while (*c) {
                char *keyName = dd->keyName[idx(*c)].name;
                len = strlen(keyName);
                memcpy(p, keyName, len);
                p += len;
                ++c;
        }
	uim->m->preedit_draw(uim, peString, NULL);
        // default cursor pos is end of preedit string,
        // no need to set it explicitly
        //uim->m->preedit_caret_pos(uim, utf8_charlen(peString));
}

static void
preedit_erase(UnihanIM *uim)
{
	TableDataPerDesktop *dd = (TableDataPerDesktop *)uim->desktopUserData;
	TableDataPerSession *ds = (TableDataPerSession *)uim->sessionUserData;

        uim->m->preedit_erase(uim);
        ds->preedit[0] = 0;
        ds->preeditLen = 0;
	ds->numCands = 0;
	ds->keybuf[0] = 0;
	ds->sp_flag = 0;
	if (ds->frqCandList) {
		free(ds->frqCandList);
		ds->frqCandList = NULL;
	}
	sp_clear_InputCell(uim);
	if (dd->IllMode) {
		CF_clear_CandFactory(ds->candFactory);
	}
}


static void
commit_string(UnihanIM *uim, char *string)
{
        UnihanTCSCMode tcsc;

        //preedit_erase(uim);

        uim->m->tcsc_mode_get(uim, &tcsc);
        if (tcsc == UNIHAN_TCSC_NO_CONV) {
                uim->m->commit_string(uim, string);
        } else {
                char *cnvd = NULL;
                uim->m->tcsc_do_conv(uim, tcsc, string, &cnvd);
                uim->m->commit_string(uim, cnvd);
        }
	uim->m->match_phrase(uim, string);
}


static const unsigned char *
utf8_next_char(const unsigned char *in)
{
	if (*in < 128)
		return (in + 1);
	else if ((*in & 0xE0) == 0xC0)
		return (in + 2);
	else if ((*in & 0xF0) == 0xE0)
		return (in + 3);
	else if ((*in & 0xF8) == 0xF0)
		return (in + 4);
	else if ((*in & 0xFC) == 0xF8)
		return (in + 5);
	else if ((*in & 0xFE) == 0xFC)
		return (in + 6);

	// invalid!
	return NULL;
}


static int
utf8_charlen(const unsigned char *in)
{
    int len = 0;

    while (*in) {
        ++len;
        in = utf8_next_char(in);
    }

    return len;
}

// vi:ts=4:sw=4:nowrap:cin:expandtab
